package ar.edu.itba.it.cg.grupo01.impl;

import java.awt.Graphics;
import java.awt.Panel;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.WindowConstants;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

import ar.edu.itba.it.cg.grupo01.api.image.Image;
import ar.edu.itba.it.cg.grupo01.impl.RayTracer.RayTracerOptions;
import ar.edu.itba.it.cg.grupo01.impl.parser.SceneDTO;
import ar.edu.itba.it.cg.grupo01.impl.parser.SceneParser;
import ar.edu.itba.it.cg.grupo01.impl.scene.SceneImpl;

public class RayTracerRunner {

	private static final int DEPTH = 7;

	public static void main(final String[] args) {

		RayTracerRunner rayTracerRunner = new RayTracerRunner();
		try {
			rayTracerRunner.run(args);
		} catch (Throwable t) {
			System.err.println("Unexpected error: " + t.getMessage());
		}

	}

	private void run(String[] args) {

		Options options = buildOptions();

		// GNU parser is used as options have more than one character
		CommandLineParser parser = new GnuParser();
		CommandLine line;
		try {
			// parse the command line arguments
			line = parser.parse(options, args);
		} catch (ParseException exp) {
			// oops, something went wrong
			System.err.println("Parsing failed.  Reason: " + exp.getMessage());
			printHelp(options);
			return;
		}

		if (line.hasOption("help")) {
			printHelp(options);
			return;
		}

		if (!line.hasOption("i")) {
			System.err
					.println("Parsing failed.  Reason: Missing required option: i");
			return;
		}

		RayTracerOptions rayCasterOptions;
		try {
			rayCasterOptions = buildRayCasterOptions(line);
		} catch (ParseException e) {
			System.err.println(e.getMessage());
			return;
		}

		run(rayCasterOptions);

	}

	private void run(RayTracerOptions rayCasterOptions) {
		long startTime = System.nanoTime();
		SceneParser parser = new SceneParser();

		SceneDTO scene = new SceneDTO();

		parser.parse(rayCasterOptions.getSceneName(), scene);

		scene.build();
		SceneImpl imageScene = new SceneImpl(scene.getScene(), scene
				.getPlanes(), scene.getLights());

		RayTracer rayTracer = new RayTracer(imageScene, scene.getCamera()
				.build(), DEPTH);

		Image image;

		if (scene.getAntiAliasing() > 0) {
			image = rayTracer.drawImageAntiAliased(rayCasterOptions,
					BufferedImage.TYPE_INT_BGR, scene.getAntiAliasing());
		} else {
			image = rayTracer.drawImage(rayCasterOptions,
					BufferedImage.TYPE_INT_RGB);
		}

		BufferedImage bi = image.getBufferedImage();

		String outputFileName = rayCasterOptions.getOutputFile();

		File out = new File(outputFileName);

		String[] formatHolder = outputFileName.split("\\.");

		try {
			ImageIO.write(bi, formatHolder[1], out);
		} catch (IOException e) {
			System.err
					.println("Something bad happened when trying to save the image: "
							+ e.getMessage());
		}

		if (rayCasterOptions.isShowImage()) {
			showImage(out, scene.getCamera().getxResolution(), scene
					.getCamera().getyResolution());
		}

		long ellapsedTime = System.nanoTime() - startTime;
		if (rayCasterOptions.isShowTime()) {
			System.out.println("Time taken: " + (ellapsedTime / 1E9)
					+ " seconds");
		}

	}

	private void showImage(File out, int width, int height) {

		JFrame frame = new JFrame("Display image");
		Panel panel = new ShowImage(out);
		frame.getContentPane().add(panel);
		frame.setSize(width, height);
		frame.setVisible(true);
		frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
	}

	@SuppressWarnings("serial")
	public static final class ShowImage extends Panel {
		BufferedImage image;

		public ShowImage(File input) {
			try {
				image = ImageIO.read(input);
			} catch (IOException ie) {
				System.out.println("Error showing image: " + ie.getMessage());
			}
		}

		public void paint(Graphics g) {
			// Flip the image horizontally
			AffineTransform tx = AffineTransform.getScaleInstance(-1, 1);
			tx.translate(-image.getWidth(null), 0);
			AffineTransformOp op = new AffineTransformOp(tx,
					AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
			BufferedImage newImage = op.filter(image, null);
			g.drawImage(newImage, 0, 0, null);
		}
	}

	private RayTracerOptions buildRayCasterOptions(CommandLine line)
			throws ParseException {

		// TODO sacar los ifs de la muerte

		String sceneName = line.getOptionValue("i");

		if (!sceneName.endsWith(".sc")) {
			throw new ParseException("Scene name must en with .sc. Got: "
					+ sceneName);
		}

		RayTracerOptions rayTracerOptions = new RayTracerOptions(sceneName);

		if (line.hasOption("o")) {
			String outputFile = line.getOptionValue("o");
			if (!outputFile.matches("[a-zA-Z0-9_]+\\.(png|bmp)")) {
				throw new ParseException(
						"Output file must letters and numbers only and be in bmp or png format. Got: "
								+ outputFile);
			}

			rayTracerOptions.setOutputFile(line.getOptionValue("o"));
		}

		if (line.hasOption("time")) {
			rayTracerOptions.setShowTime(true);
		}
		if (line.hasOption("show")) {
			rayTracerOptions.setShowImage(true);
		}

		if (line.hasOption("progress")) {
			rayTracerOptions.setShowProgress(true);
		}

		// if (line.hasOption("gui")) {
		// rayTracerOptions.setShowGUI(true);
		// }

		// if (line.hasOption("dof")) {
		// String dofValue = line.getOptionValue("dof");
		// try {
		// int dof = Integer.valueOf(dofValue);
		// if (dof < 0) {
		// throw new ParseException(
		// "Dof should be greater than 0. Got: " + dofValue);
		// }
		//
		// rayTracerOptions.setDof(dof);
		// } catch (NumberFormatException e) {
		// throw new ParseException(
		// "Dof should be an integer value. Got: " + dofValue);
		// }
		// }

		return rayTracerOptions;
	}

	private void printHelp(Options options) {
		HelpFormatter formatter = new HelpFormatter();
		formatter.printHelp("raytracer", options);
	}

	private Options buildOptions() {
		// create Options object
		Options options = new Options();

		// add help option (-help)
		options.addOption(new Option("help", "print this message"));
		// add time option (-time)
		options.addOption(new Option("time",
				"shows the time taken by the raytracer to generate the image"));
		// add progress option (-progress)
		options.addOption(new Option("progress", "show execution progress"));
		// add show option (-show)
		options.addOption(new Option("show", "pop up image after rendering"));
		// add GUI option (-gui)
		// options.addOption(new Option("gui",
		// "to select configuration"));
		// add input file option (-i)
		options.addOption(OptionBuilder.withArgName("scene").hasArg()
				.withDescription("input scene").create("i"));
		// add output file option (-o)
		options.addOption(OptionBuilder.withArgName("filename").hasArg()
				.withDescription("output image").create("o"));
		// options.addOption(OptionBuilder.withArgName("fieldDepth").hasArg()
		// .withDescription("field depth [default 1]").create("dof"));
		return options;
	}

}
